#include <Kernel/GlobalData.h>
#include <Memory/Paging.h>
#include <IntExcept/Debug.h>
#include <Utility/CLib.h>
#include <Utility/intrinsic.h>
#include <ProcessThread/ProcessThread.h>
#include <IntExcept/Error.h>

/*
@brief 处理缺页故障
@param `dwPrivilegeLevel` 缺页故障发生的特权级
@return 状态码
*/
BOOL HandlePageFault(DWORD dwPrivilegeLevel)
{
    DWORD_PTR dwCR2 = READ_CR2_32();
    DWORD dwPDEIndex = RESOLVE_PDE_INDEX(dwCR2);
    DWORD dwPTEIndex = RESOLVE_PTE_INDEX(dwCR2);
    LPPageDirectoryEntry lpUserPDE = NULL;
    LPPageDirectoryEntry lpKernelPDE = NULL;
    LPPageTableEntry lpUserPTE = NULL;
    LPPageTableEntry lpKernelPTE = NULL;
    if (dwPDEIndex < 768)
    {
        goto page_fault_in_user_space;
    }
    else if (dwPDEIndex < 1022)
    {
        goto page_fault_in_kernel_space;
    }
    else if (dwPDEIndex == 1022)
    {
        goto page_fault_in_kernel_page_table;
    }
    else /* dwPDEIndex == 1023 */
    {
        goto page_fault_in_user_page_table;
    }

/* 用户缺页 */
page_fault_in_user_space:
    /* 访问内核地址范围缺页发生时，对页目录和页表的操作都是针对主控页目录进行的 */
    /* 在用户页表中注册缺失的页 */
    lpUserPTE = (LPVOID)GENERATE_VIRTUAL_ADDRESS(1023, dwPDEIndex, dwPTEIndex * 4); /* 需要填补的页目录项 */
    if ((*lpUserPTE & PTE_ALLOCATED) == 0) /* 该页内存未分配 */
    {
        SetLastError(ERROR_ACCESS_DENIED);
        return FALSE; /* 虚拟页面必须提前分配 */
    }
    *lpUserPTE |= PTE_USER; /* 用户页表项 */
    return AllocateFrame(lpUserPTE); /* 为用户分配物理页框 */
/* 内核空间发生缺页 */
page_fault_in_kernel_space:
    lpUserPDE = (LPVOID)GENERATE_VIRTUAL_ADDRESS(1023, 1023, dwPDEIndex * 4); /* 用户 PDE */
    lpKernelPDE = (LPVOID)GENERATE_VIRTUAL_ADDRESS(1022, 1022, dwPDEIndex * 4); /* 内核 PDE */
    KERNEL_ASSERT((*lpKernelPDE >> 12) == (*lpUserPDE >> 12) || (*lpUserPDE & PDE_PRESENT) == 0, "MEMORY CORRUPTION. User's page directory inconsistent with kernel's page directory.\r\n"); /* 若用户 PDE 有效，则其中记录的物理页框号必须和内核 PDE 完全一致 */
    lpKernelPTE = (LPVOID)GENERATE_VIRTUAL_ADDRESS(1022, dwPDEIndex, dwPTEIndex * 4); /* 根据内核目录找到页表项对应的线性地址 */
    if ((*lpKernelPTE & PTE_PRESENT)) /* 内核页表中该页并未缺失，说明缺页是由于内核目录和用户目录未同步导致的 */
    {
        *lpUserPDE = *lpKernelPDE; /* 内核页目录项填补用户页目录项 */
        return TRUE;
    }
    else if ((*lpKernelPTE & PTE_ALLOCATED)) /* 内核页表中页表项缺失，且该项已经分配 */
    {
        if (!AllocateFrame(lpKernelPTE)) /* 为发生缺页的地址分配物理页框 */
        {
            return FALSE;
        }
        *lpUserPDE = *lpKernelPDE; /* 内核页目录项填补用户页目录项 */
        return TRUE;
    }
    else /* 访问的页面未预分配 */
    {
        SetLastError(ERROR_ACCESS_DENIED);
        return FALSE;
    }
/* 内核页表发生缺页 */
page_fault_in_kernel_page_table:
    if (dwPTEIndex < 768)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    // KERNEL_ASSERT(dwPTEIndex >= 768, "Page table entry does not belong to kernel space.\r\n");
    lpKernelPDE = (LPVOID)GENERATE_VIRTUAL_ADDRESS(1022, 1022, dwPTEIndex * 4); /* 内核 PDE */
    *lpKernelPDE |= PDE_WRITEABLE;
    // KERNEL_ASSERT(FRAME_ALLOCATE_SUCCESS == AllocateFrame(lpKernelPDE), "Fails to allocate physical frame for kernel.\r\n"); /* 分配物理页框，内核分配必须成功 */
    if (!AllocateFrame(lpKernelPDE))
    {
        return FALSE;
    }
    memset((LPVOID)(dwCR2 & 0xFFFFF000), 0, PAGE_SIZE); /* 页表必须清零 */
    return TRUE;
/* 用户页表发生缺页 */
page_fault_in_user_page_table:
    if (dwPTEIndex >= 768)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    // KERNEL_ASSERT(dwPTEIndex < 768, "Page table entry does not belong to user space.\r\n");
    lpUserPDE = (LPVOID)GENERATE_VIRTUAL_ADDRESS(1023, 1023, dwPTEIndex * 4); /* 用户 PTE */
    *lpUserPDE |= PDE_WRITEABLE | PDE_USER; /* 用户页表的 PDE 需要设置成 PTE_USER，否则会触发异常 */
    if (!AllocateFrame(lpUserPDE))
    {
        return FALSE;
    }
    // KERNEL_ASSERT(AllocateFrame(lpUserPDE) == FRAME_ALLOCATE_SUCCESS, "Physical memory pool runs out.\r\n");
    memset((LPVOID)(dwCR2 & 0xFFFFF000), 0, PAGE_SIZE);
    return TRUE;
}